import mjs.processing.mobile.msound.*;
import mjs.processing.mobile.mvideo.*;
import processing.image2.*;

/**
 * MColorIndentifier
 *
 * This sketch allow the user identified the color of a element capture with
 * the camera and spoke the result if sound is enable.
 *
 * This initial version takes the snapshot and count how many pixels of the
 * color table are present, the color with the major number of appearances 
 * is the color of the element in the snapshot, when the identification is 
 * finished a sound with the color name is played.
 * 
 * To perform a better identification the user must capture a image of the 
 * part of the element that want to identify the color, panoramic photos 
 * don't release a accurate values, also the user must provide a good 
 * illumination to the element
 *
 * The current version only recognize the following colors : 
 *   Red, Green, Blue, Yellow, White and Black.
 *
 * The main goal of this sketch is provide a color identification for persons 
 * with vision problems.
 * 
 * @author Mary Jane Soft - Marlon J. Manrique
 * @version 0.1 (2007,06,16)
 *
 * $Id$
 */
 
// Colors RGB Values
int[][] colorRBG =
{
  { 255,   0,   0  }, // red
  {   0, 255,   0  }, // green
  {   0,   0, 255  }, // blue
  { 255, 255,   0  }, // yellow
  { 255, 255, 255  }, // white
  {   0,   0,   0  }  // black
};

// Color Names
String[] colorName =
{
  "Red",
  "Green",
  "Blue",
  "Yellow",
  "White",
  "Black"
};

// User Message
String instruction = "";
String info = "";

// Capture Object
MCapture capture;

// The capture image
PImage theImage;

// Number of colors in the table
int numColors;

// Video Size
int videoX;
int videoY;
int videoWidth;
int videoHeight;

// Text Positions
int instructionX;
int instructionY;
int infoX;
int infoY;

// Sound enable
boolean soundEnable;

// Sounds
MSound pressSound;
MSound colorSound;

/**
 * Start the application.
 *
 * Create camera, load fonts
 */
void setup()
{
  // Set text and color attributes
  textFont(loadFont());
  textAlign(CENTER);
  background(0);

  // Set the num of colors into the table
  numColors = colorName.length;

  // Init capture 
  capture = new MCapture(this);

  // Calculate Video Size
  videoWidth = capture.width() / 2;
  videoHeight = capture.height() / 2;
  capture.size(videoWidth,videoHeight);

  // calculate video location
  videoX = (width - videoWidth)/2;
  videoY = (height - videoHeight)/2;

  // Show the video capture at the center of the screen
  capture.location(videoX,videoY);
  capture.hide();

  // Initial message
  instruction = "Press 2 to Show Camera";
  instructionX = width/2;
  instructionY = videoY - 10;
  info = "Ready";
  infoX = instructionX;
  infoY = videoY + capture.height() + 15;

  // Load sounds
  //pressSound = new MSound("press.mp3");
  //colorSound = new MSound("color.mp3");
  soundEnable = MSoundManager.mediaTypeSupported("audio/mp3");
}

/**
 * Draw camera and info
 */
void draw()
{
  // Repaint all
  background(0);

  // Show Messages
  text(instruction,instructionX,instructionY);
  text(info,infoX,infoY);  

  // If a image is available draw it
  if(theImage != null)
    image(theImage,videoX,videoY);
}

/**
 * Perform user actions
 */
void keyPressed()
{
  // The capture Image
  PImage snapshot = null;

  // Begin color extraction ?
  boolean beginProcess = false; 

  // Show the capture video
  if(key == '2')
  {
    instruction = "Press 5 to Capture Image";
    info = "Camera";
    theImage = null;
    noLoop();
    capture.show();
  }
  // Grab the image from the camera
  else if(key == '5')
  {
    beginProcess = true;
    loop();
    snapshot = capture.snapshot();
  }

  // If a image is ready for process
  if(beginProcess)
  {
    // Hide the video
    capture.hide();
    // Try to identify the color
    identifyColor(snapshot);
  }
}

/**
 * Identify color and show the result
 *
 * @param snapshot Image to process
 */
void identifyColor(PImage snapshot)
{
  // Change message
  instruction = "Processing Image";

  // Calculate the color of the image
  int[] colorsTable = calculateColors(snapshot);

  // Set the max color value to min value  
  int maxColor = Integer.MIN_VALUE;

  // Set the index color like unknown
  int maxColorIndex = -1;

  // Get the max color index
  for(int i=0; i<numColors; i++)    
    if(colorsTable[i] > maxColor)
    {
      maxColor = colorsTable[i];
      maxColorIndex = i;
    }

  // Update the message with the color identified  
  instruction = "Press 2 to Show Camera";    
  info = "Color : " + colorName[maxColorIndex];

  // Speech the color
  speakColor(maxColorIndex);
}

/**
 * Speak the color
 *
 * @param the color index of the main table
 */
void speakColor(int colorIndex)
{  
  // If no sound, do nothing
  if(!soundEnable)
    return;

  // Set filename
  String cName = colorName[colorIndex].toLowerCase();
  String colorSoundFile = cName + ".mp3";

  // Load the sound and play it
  MSound colorNameSound = new MSound(colorSoundFile);
  colorNameSound.play();
}

/**
 * Calculate the color table of the image
 *
 * @param snapshot The image
 * @return a array with the number of pixels of each color
 */
int[] calculateColors(PImage snapshot)
{
  // Create a empty array with the current colors
  int[] colorsTable;
  colorsTable = new int[numColors];

  // Create an resized image to fit into the scree
  info = "Creating Image to Analize";  
  theImage = new PImage(videoWidth,videoHeight);
  theImage.copy(snapshot,0,0,snapshot.width,snapshot.height,
    0,0,videoWidth,videoHeight);

  // Create a PImage2 to get the pixels
  PImage2 pImage2 = new PImage2(theImage);

  // Color components, number of pixels of the image
  int r=0,g=0,b=0;
  int numPixels = pImage2.pixels.length;

  // The index color inside the table
  int colorIndex;

  // For each pixel into the image
  for(int i=0; i<numPixels; i++)
  {
    // Grab the RGB values
    r = ((pImage2.pixels[i] & 0x00FF0000) >> 16);
    g = ((pImage2.pixels[i] & 0x0000FF00) >> 8);
    b = pImage2.pixels[i] & 0x000000FF;

    // Get the color index of the table according with the RGB
    colorIndex = colorIndex(r,g,b);

    // Add 1 to the colorsTable
    colorsTable[colorIndex]++;

    // Update the pixels of the image with the color of the table
    pImage2.pixels[i] = 0xFF000000 
      | colorRBG[colorIndex][0] << 16
      | colorRBG[colorIndex][1] << 8
      | colorRBG[colorIndex][2];

    // Show Progress
    info = "Processing Image " + ((i+1)*100)/numPixels + "%";      
  }

  // Update the snapshot with the transformed image
  theImage = pImage2;

  // Return the colorsTable  
  return colorsTable;
}

/**
 * Get the color index inside the table according with the 
 * minimum distance between each color and the RGB 
 *
 * @param r red component
 * @param g green component
 * @param b blue component
 *
 * @return index of color near to the given color
 */ 
int colorIndex(int r, int g, int b)
{
  // Set the min distance to the max value
  double minDistance = Double.MAX_VALUE;

  // Set the index color like unknown
  int colorIndex = -1;

  // For each color into the table
  for(int i=0; i<numColors; i++)
  {
    // Get the difference between color components
    int dr = r - colorRBG[i][0];
    int dg = g - colorRBG[i][1];
    int db = b - colorRBG[i][2];

    // Calculate the distance between colors using 3D distance
    // each color component is a coord
    double distance = Math.sqrt(dr*dr + dg*dg + db*db);

    // If distance id equal to 0, the color was identified
    if(distance == 0)
    {
      colorIndex = i;
      break;
    }

    // If the distance is less that current min distance
    if(distance < minDistance)
    {
      // Update the colorIndex
      minDistance = distance;
      colorIndex = i;
    }
  }

  // Return the color Index more near to the given color
  return colorIndex;
}
